/**
 * 
 */
package org.rollinitiative.d20.entity.classes;

import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;

import javax.xml.namespace.QName;

import org.apache.commons.configuration.XMLConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.XmlException;
import org.exist.xmldb.XQueryService;
import org.rollinitiative.d20.Adjustment;
import org.rollinitiative.d20.Adjustment.AdjustmentSource;
import org.rollinitiative.d20.entity.InvalidEntityException;
import org.rollinitiative.d20.entity.Save;
import org.rollinitiative.d20.entity.skills.Skill;
import org.rollinitiative.d20.entity.skills.SkillsContentBridge;
import org.rollinitiative.d20.persist.PersistenceBridge;
import org.rollinitiative.d20.schema.d20.SavesSetType;
import org.rollinitiative.d20.schema.d20Class.ClassType;
import org.rollinitiative.d20.schema.d20Class.LevelType;
import org.rollinitiative.d20.schema.d20Class.AttackBonusType;
import org.rollinitiative.d20.schema.d20Class.TalentRefListType;
import org.rollinitiative.d20.schema.talent.TalentType;
import org.xmldb.api.base.Collection;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;

/**
 * @author bebopjmm
 * 
 */
public class ClassContentBridge extends PersistenceBridge
{
    final static Log LOG = LogFactory.getLog(ClassContentBridge.class);

    HashMap<String, ClassType> classCache = new HashMap<String, ClassType>();
    
    SkillsContentBridge skillBridge = null;

    Collection classesCollection;


    public ClassContentBridge(XMLConfiguration config)
    {
        super(config);
    }

    public void setSkillContentBridge(SkillsContentBridge skillBridge) {
        this.skillBridge = skillBridge;
    }

    public void loadCollection(String campaign)
    {
        try {
            connect(campaign);
            classesCollection = retrieveCollection("classes");
            xqueryService = (XQueryService) classesCollection.getService("XQueryService", "1.0");
        } catch (XMLDBException ex) {
            LOG.error("Failed to load the classes collection");
        }
    }


    public ClassType retrieveClass(String classID) throws InvalidEntityException
    {
        // Check Cache first
        if (classCache.containsKey(classID)) {
            LOG.debug("Cache contained requested class: " + classID);
            return classCache.get(classID);
        }

        try {
            LOG.debug("Querying for requested race: " + classID);
            String classQuery = config.getString("classBridge.queries.classByID");
            classQuery = classQuery.replace("#ID#", classID);
            LOG.debug("Parameterized Query: " + classQuery);
            ResourceSet results = query(classQuery);
            if (results.getSize() < 1) {
                throw new InvalidEntityException("Specified Class not Found: " + classID);
            }

            ClassType d20Class = ClassType.Factory.parse(results.getResource(0).getContent()
                    .toString(), xmlOptions);
            classCache.put(classID, d20Class);
            return d20Class;
        } catch (XmlException e) {
            LOG.error("Failure to parse requested Class: " + classID, e);
            throw new InvalidEntityException("Failure to retrieve specified Class: " + classID);
        } catch (XMLDBException e) {
            LOG.error("Failure to parse requested Class: " + classID, e);
            throw new InvalidEntityException("Failure to retrieve specified Class: " + classID);
        }
    }


    /**
     * This method returns the BaseAttackBonus array for the class identified by classID at the
     * requested level.
     * 
     * @param classID String ID of the D20 Actor Class to query
     * @param level Class level being queried (1-20)
     * @return integer array of BaseAttackBonus. Number of elements indicates the number of attacks
     *         possible and values are BaseAttackBonus for each
     * @throws InvalidEntityException No D20 Actor Class of classID could be found
     */
    public int getBaseAttackBonus(String classID, int level) throws InvalidEntityException
    {
        ClassType d20Class = retrieveClass(classID);
        if (d20Class == null) {
            LOG.error("NULL classType returned!!");
            return -1;
        }
        LevelType[] levels = d20Class.getAdvancement().getLevelEntryArray();
        LOG.info("Total number of levels in class, " + classID + " = " + levels.length);
        for (int i = 0; i < levels.length; i++) {
            if (levels[i].getLevelNum().intValue() == level) {
                return levels[i].getBaseAttackBonus().intValue();
            }
        }
        LOG.error("Failed to locate level " + level + "info for class, " + classID);
        return -1;
    }


    /**
     * @param classID
     * @param level
     * @return
     * @throws InvalidEntityException
     */
    public EnumMap<Save, Adjustment> getSaves(String classID, int level)
            throws InvalidEntityException
    {
        EnumMap<Save, Adjustment> saves = new EnumMap<Save, Adjustment>(Save.class);
        for (Save s : EnumSet.range(Save.FORTITUDE, Save.WILL)) {
            saves.put(s, new Adjustment(AdjustmentSource.INHERENT, 0, "Class"));
        }

        ClassType d20Class = retrieveClass(classID);
        if (d20Class == null) {
            LOG.error("NULL classType returned!!");
            return saves;
        }

        LevelType[] levels = d20Class.getAdvancement().getLevelEntryArray();
        String adjustTag = "CLASS." + d20Class.getDescriptShort();
        LOG.info("Retrieving save values for, " + adjustTag + ", level = " + level);
        for (int i = 0; i < levels.length; i++) {
            if (levels[i].getLevelNum().intValue() == level) {
                SavesSetType classSaves = levels[i].getSaves();
                saves.put(Save.FORTITUDE, new Adjustment(AdjustmentSource.INHERENT, classSaves
                        .getFortitude().intValue(), adjustTag));
                LOG.debug("-- FORT = " + classSaves.getFortitude());
                saves.put(Save.REFLEX, new Adjustment(AdjustmentSource.INHERENT, classSaves
                        .getReflex().intValue(), adjustTag));
                LOG.debug("-- REFL = " + classSaves.getReflex());
                saves.put(Save.WILL, new Adjustment(AdjustmentSource.INHERENT, classSaves.getWill()
                        .intValue(), adjustTag));
                LOG.debug("-- WILL = " + classSaves.getWill());
                return saves;
            }
        }
        LOG.error("Failed to locate level " + level + "info for class, " + classID);
        return saves;
    }


    public String getDescriptiveNameLong(String classID) throws InvalidEntityException
    {
        ClassType d20Class = retrieveClass(classID);
        if (d20Class == null) {
            LOG.error("NULL classType returned!!");
            return "";
        }
        return d20Class.getDescriptLong();
    }


    public String getDescriptiveNameShort(String classID) throws InvalidEntityException
    {
        ClassType d20Class = retrieveClass(classID);
        if (d20Class == null) {
            LOG.error("NULL classType returned!!");
            return "";
        }
        return d20Class.getDescriptShort();
    }


    public TalentType[] getTalents(String classID, int level) throws InvalidEntityException
    {
        if (level < 0) {
            return new TalentType[0];
        }

        ClassType d20Class = retrieveClass(classID);
        LevelType[] levels = d20Class.getAdvancement().getLevelEntryArray();
        if (level >= levels.length) {
            return new TalentType[0];
        }
        else {
            return levels[level].getTalents().getTalentArray();
        }
    }
    
    public Skill[] getClassSkills(String classID) throws InvalidEntityException
    {
        ClassType d20Class = retrieveClass(classID);
        QName[] skillIDs = d20Class.getSkillList().getSkillIDArray();
        Skill[] skills = new Skill[skillIDs.length];
        for (int i = 0; i < skills.length; i++) {
            skills[i] = skillBridge.retrieveSkill(skillIDs[i].getLocalPart());
        }
        return skills;
    }
    
    public boolean isClassSkill(String skillID, String classID) throws InvalidEntityException
    {
        ClassType d20Class = retrieveClass(classID);
        QName[] skillIDs = d20Class.getSkillList().getSkillIDArray();
        for (QName name : skillIDs) {
            if (name.getLocalPart().equalsIgnoreCase(skillID)) {
                return true;
            }
        }
        
        return false;
    }
}
